package com.chaintor.demo.math;

import org.apache.commons.math3.distribution.RealDistribution;
import org.apache.commons.math3.distribution.UniformRealDistribution;
import org.apache.commons.math3.util.FastMath;

import java.util.Random;
import java.util.Vector;

/**
 * 差分进化类
 */
public class DifferentialEvolution {
    private double absoluteAcuracy = 1E-12;
    static final double g_defaultLowerConstraint = - Double.MAX_VALUE;
    static final double g_defaultUpperConstarint = Double.MAX_VALUE;
    private DiffEvolutionOptimizable m_cost;
    private int m_populationSize;
    private double m_F = 0.8;
    private double m_CR = 0.9;
    private  int m_bestAgentIndex = 0;
    private double m_minCost = Double.POSITIVE_INFINITY;
    private double m_secCost = Double.POSITIVE_INFINITY;
    private boolean m_shouldCheckConstraints = true;
    private int m_count = 0;
    //m_callback(callback),
    //m_terminationCondition(terminationCondition)


    private int m_numberOfParameters;
    private Vector<double[]> m_population;
    private double[] m_minCostPerAgent;
    private Vector<Constraints> m_constraints;

    //上清特殊
    private double[] m_alpha;

    public DifferentialEvolution(DiffEvolutionOptimizable costFunction, int populationSize) throws Exception{
        this(costFunction,populationSize,true);
    }

    public DifferentialEvolution(DiffEvolutionOptimizable costFunction, int populationSize,double absoluteAccuracy) throws Exception{
        this(costFunction,populationSize,true);
        this.absoluteAcuracy = absoluteAccuracy;
    }


    public DifferentialEvolution(DiffEvolutionOptimizable costFunction, int populationSize, boolean shouldCheckConstraints ) throws Exception {
        m_cost = costFunction;
        m_populationSize = populationSize;
        m_shouldCheckConstraints = shouldCheckConstraints;
        if (m_populationSize < 4) throw new Exception("populationSize so small.");
        m_numberOfParameters = m_cost.numberOfParameters();
        m_population = new Vector(m_populationSize);
        for (int i=0;i<m_populationSize;i++){
            m_population.addElement(new double[m_numberOfParameters]);
        }
        m_minCostPerAgent = new double[m_populationSize];
        m_constraints = m_cost.GetConstraints();

        m_alpha = new double[m_populationSize];

    }


    /**
     * 初始化平均分布随机数参数集及从中挑选最优的那一组参数
     */
    public void initPopulation(){
        // Init population based on random sampling of the cost function
        RealDistribution[] distribution = new RealDistribution[m_numberOfParameters];
        for (int i = 0; i < m_numberOfParameters; i++) {
            if (m_constraints.get(i).isConstrained) {
                distribution[i] =  new UniformRealDistribution(m_constraints.get(i).lower,m_constraints.get(i).upper);
            } else {
                distribution[i] = new UniformRealDistribution(g_defaultLowerConstraint, g_defaultUpperConstarint);
            }

        }

        for (double[] agent : m_population) {
            for (int i = 0; i < m_numberOfParameters; i++) {
                agent[i] = distribution[i].sample();
            }
        }

        // Initialize minimum cost, best agent and best agent index
        for (int i = 0; i < m_populationSize; i++) {
            m_minCostPerAgent[i] = m_cost.evaluteCost(m_population.get(i));
            m_alpha[i] = m_cost.getAlpha();
            if (m_minCostPerAgent[i] < m_minCost) {
                m_minCost = m_minCostPerAgent[i];
                m_secCost = m_minCost;
                m_bestAgentIndex = i;
            }
        }
    }

    /**
     * 对所有的随机参数集合进行一次优化
     * 在随机集中随机选择不同组的相同位子的参数进行合成
     * 如果新和成的一组参数优于老的参数就替换
     */
    public void selectionAndCorssing(){
        RealDistribution distribution =  new UniformRealDistribution(0,m_populationSize);
        double minCost = m_minCostPerAgent[0];
        int bestAgentIndex = 0;

        for (int x = 0; x < m_populationSize; x++){
            // For x in population select 3 random agents (a, b, c) different from x
            int a = x;
            int b = x;
            int c = x;

            // Agents must be different from each other and from x
            while (a == x || b == x || c == x || a == b || a == c || b == c) {
                a = (int)distribution.sample();
                b = (int)distribution.sample();
                c = (int)distribution.sample();
            }

            // Form intermediate solution z
            double[] z = new double[m_numberOfParameters];
            for (int i = 0; i < m_numberOfParameters; i ++) {
                z[i] = m_population.get(a)[i] + m_F * (m_population.get(b)[i] - m_population.get(c)[i]);
            }

            // Chose random R
            RealDistribution distributionParam = new UniformRealDistribution(0, m_numberOfParameters);
            int R = (int)distributionParam.sample();

            // Chose random r for each dimension
            double[] r = new double[m_numberOfParameters];
            RealDistribution distributionPerX = new UniformRealDistribution(0, 1);
            for (int i=0;i<m_numberOfParameters;i++) {
                r[i] = distributionPerX.sample();
            }

            double[] newX = new double[m_numberOfParameters];

            // Execute crossing
            for (int i = 0; i < m_numberOfParameters; i++) {
                if (r[i] < m_CR || i == R) {
                    newX[i] = z[i];
                }else {
                    newX[i] = m_population.get(x)[i];
                }
            }

            // Check if newX candidate satisfies constraints and skip it if not.
            // If agent is skipped loop iteration x is decreased so that it is ensured
            // that the population has constant size (equal to m_populationSize).
            if (m_shouldCheckConstraints && !checkConstraints(newX)) {
                x--;
                continue;
            }

            // Calculate new cost and decide should the newX be kept.
            double newCost = m_cost.evaluteCost(newX);
            if (newCost < m_minCostPerAgent[x]) {
                m_population.set(x,newX);// = newX;
                m_minCostPerAgent[x] = newCost;
                m_alpha[x] = m_cost.getAlpha();
            }

            // Track the global best agent.
            if (m_minCostPerAgent[x] < minCost){
                minCost = m_minCostPerAgent[x];
                bestAgentIndex = x;
            }
        }

        m_minCost = minCost;
        m_bestAgentIndex = bestAgentIndex;

    }


    /**
     * 返回最优的成本
     * @return
     */
    public double getBestCost() {
        return m_minCostPerAgent[m_bestAgentIndex];
    }

    /**
     * 开始优化计算返回最优解
     * @param iterations
     */
    public double[] optimize(int iterations ){
        initPopulation();
        // Optimization loop
        for (int i = 0; i < iterations; i++) {
            // Optimization step
            selectionAndCorssing();
            m_count++;
            if (m_minCost != m_secCost && FastMath.abs(m_minCost-m_secCost)<absoluteAcuracy){
                return getBestAgent();
            }else{
                m_secCost = m_minCost;
            }
        }
        return  getBestAgent();
    }

    /**
     * 检查参数是否符合要求
     * @param agent
     * @return
     */
    private boolean checkConstraints(double[] agent) {
        for (int i = 0; i < agent.length; i++)
        {
            if (!m_constraints.get(i).check(agent[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * 返回最优的成本
     * @return
     */
    public double[] getBestAgent(){
        double[] rv = new double[m_numberOfParameters+1];
        for(int i=0;i<m_numberOfParameters;i++){
            rv[i] = m_population.get(m_bestAgentIndex)[i];
        }
        rv[m_numberOfParameters] = m_alpha[m_bestAgentIndex];
        return rv;
    }

}
